package org.fjsei.yewu.resolver;

import io.minio.PutObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.messages.Retention;
import io.minio.messages.RetentionMode;
import lombok.extern.slf4j.Slf4j;
import md.system.User;
import md.system.UserRepository;
import org.fjsei.yewu.config.MinioConfig;
import org.fjsei.yewu.config.property.TusProperties;
import me.desair.tus.server.TusFileUploadService;
import me.desair.tus.server.exception.TusException;
import me.desair.tus.server.upload.UploadInfo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.fjsei.yewu.util.Base;
import org.fjsei.yewu.util.MinioUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**其实 这个自身也是配置类new的 Bean() 实例。
 * @desc 断点续传的逻辑
 * TUS协议 介绍：     https://gitee.com/mirrors/tus/blob/main/docs/faq.md
 * @created 2021/11/19 11:42 上午
 **/
@Slf4j
public class TusFileUploadResolver {
    private final TusFileUploadService tusFileUploadService;
    private final TusProperties tusProperties;
    @Autowired
    private MinioUtil minioUtil;
    @Autowired
    private Base base;
    @Autowired
    private MinioConfig prop;
    protected final UserRepository userRepository;

    public TusFileUploadResolver(TusFileUploadService tusFileUploadService, TusProperties tusProperties, UserRepository userRepository) {
        this.userRepository = userRepository;
        this.tusFileUploadService = tusFileUploadService;
        this.tusProperties = tusProperties;
    }
    /**
     *文档 https://uppy.io/docs/tus/ 文件断点续传的协议-Tus 协议  https://www.jianshu.com/p/57405bc1bc46
     * @return 前端需要存储的文件可访问路径。
     * 真正上传完成的 都要进入这个 method = {RequestMethod.PATCH}
     * */
    public String uploadPatch(HttpServletRequest request, HttpServletResponse response) {
        User user=base.checkAuth();
        if(user==null){
            response.setStatus(403);
            return null;     //没有登录的
        }
        String fileOss = null;
        try {
            tusFileUploadService.process(request, response);
            final String uploadURI = request.getRequestURI();
            UploadInfo uploadInfo = null;
            try {
                uploadInfo = tusFileUploadService.getUploadInfo(uploadURI);
            }
            catch (IOException | TusException e) {
                throw new RuntimeException("获取上传信息失败", e);
            }

            if (uploadInfo != null && !uploadInfo.isUploadInProgress()) {
                final InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(uploadURI);
                boolean putOK=false;
                try {
                    final String appUploadDirectory = tusProperties.getAppUploadDirectory().toString();
                    final Path outPut = Paths.get(appUploadDirectory).resolve(uploadInfo.getFileName());
                    //将后端已经完成端点续传的最终的文件 存入指定的MinIO存储；
                    fileOss=minioUtil.uploadForTUS(uploadedBytes,uploadInfo,user);
                    log.info("TUS上传 {} 到={}", uploadInfo.getFileName(), fileOss);
                    putOK=true;
                }
                catch (Exception e) {
                    log.error("MinIO storage failed: {}", e.getMessage());
                    try {
                        String errorMsg;
                        if (e.getMessage().contains("Connection") || e.getMessage().contains("Connect") ||
                                e.getMessage().contains("refused") || e.getMessage().contains("timeout")) {
                            errorMsg = "Storage service connection failed, please check MinIO cluster status";
                        } else if (e.getMessage().contains("空间") || e.getMessage().contains("不足") ||
                                e.getMessage().contains("NoSpace")) {
                            errorMsg = "Insufficient storage space, unable to save file";
                        } else {
                            errorMsg = "Storage service exception: " + e.getMessage();
                        }
                        // 使用英文或者编码后的中文
                        response.setHeader("Tus2minIoUrl", errorMsg);
                    } catch (Exception ex) {
                        response.setHeader("Tus2minIoUrl", "Storage error");
                    }
                    putOK = false;
                } finally {
                    try {
                        tusFileUploadService.deleteUpload(uploadURI);
                        uploadedBytes.close();
                    } catch (Exception ex) {
                        log.warn("清理资源时发生异常: {}", ex.getMessage());
                    }
                }
            }
        } catch (TusException | IOException e) {
            log.error("TUS上传处理失败: {}", e.getMessage());
            // 这里也要设置错误信息，防止异常被吞掉
            response.setHeader("Tus2minIoUrl", "tus-exceptions");
            response.setStatus(500);
            throw new RuntimeException("上传失败，原因：" + e.getMessage(), e);
        }
        if(null!=fileOss) {
            response.setHeader("Tus2minIoUrl", fileOss);
        }
        return fileOss;
    }
    /** HEAD 走这里；
     * 创建（POST） -> 上传（PATCH） -> [可选：中断 -> 查询（HEAD） -> 恢复（PATCH）] -> 完成
     *文档 https://uppy.io/docs/tus/ 文件断点续传的协议-Tus 协议  https://www.jianshu.com/p/57405bc1bc46
     * */
    public void processHead(HttpServletRequest request, HttpServletResponse response) {
        User user=base.checkAuth();
        if(user==null){
            response.setStatus(403);
            return;     //没有登录的，若经过下面的tusFileUploadService.process(request, response)就不会有应答new UploadFileResponse("WQA.Dac", "http://sdff111/ert6ertrE"
        }
        // 检查 MinIO 连接状态
        if (!minioUtil.ossIsHealth()) {
            log.error("MinIO service unavailable or bucket does not exist");
            response.setStatus(503);
            // 使用英文避免编码问题
            response.setHeader("Tus2minIoUrl", "Storage service unavailable, connection failed");
            response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Location,Upload-Offset,Upload-Length,Tus2minIoUrl");
            return;
        }
        try {
            tusFileUploadService.process(request, response);
            final String uploadURI = request.getRequestURI();
            UploadInfo uploadInfo = null;
            try {
                uploadInfo = tusFileUploadService.getUploadInfo(uploadURI);
            }
            catch (IOException | TusException e) {
                throw new RuntimeException("get upload info", e);
            }
//            log.info("TUS上传预备 {}", uploadURI);
            if (uploadInfo != null && !uploadInfo.isUploadInProgress()) {
                final InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(uploadURI);
                try {
                    final String appUploadDirectory = tusProperties.getAppUploadDirectory().toString();
                    final Path outPut = Paths.get(appUploadDirectory).resolve(uploadInfo.getFileName());
                    String fileOss=minioUtil.uploadForTUS(uploadedBytes,uploadInfo,user);       //@上一次失败的情况，若+HEAD包:可能进这里。
//                    log.info("TUS上传fName {} uploadURI={}最后uRL={}", uploadInfo.getFileName(),uploadURI, fileOss);
                }
                catch (Exception e) {
                    throw new RuntimeException("upload空间满?", e);
                }
                uploadedBytes.close();
            }
            //if(null==uploadInfo)  response.setHeader("Tus2minIoUrl", "DO NOT TRY:uploadInfoNull");     //@被上面.process(request, response)牵着没法应答body;
        } catch (TusException | IOException e) {
            throw new RuntimeException("上传失败，原因：" + e.getMessage(), e);
        }
        //前端报错 httpStack Refused to get unsafe header "Location" Refused to get unsafe header "Upload-Offset" 上传扩展协议TUS
        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Location,Upload-Offset,Upload-Length,Tus2minIoUrl");
    }
     /** POST 走这里
     *文档 https://uppy.io/docs/tus/ 文件断点续传的协议-Tus 协议  https://www.jianshu.com/p/57405bc1bc46
     * */
    public void upload(HttpServletRequest request, HttpServletResponse response) {
        User user=base.checkAuth();
        if(user==null){
            response.setStatus(403);
            return;     //没有登录的，若经过下面的tusFileUploadService.process(request, response)就不会有应答new UploadFileResponse("WQA.Dac", "http://sdff111/ert6ertrE"
        }
        // 检查 MinIO 连接状态
        if (!minioUtil.ossIsHealth()) {
            log.error("MinIO service unavailable or bucket does not exist");
            response.setStatus(503);
            //避免前端中英文都可能的需求解码问题，Tus2minIoUrl都用英文; URLEncoder.encode(error, StandardCharsets.UTF_8.toString())
            response.setHeader("Tus2minIoUrl", "Storage service unavailable, connection failed");
            response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Location,Upload-Offset,Upload-Length,Tus2minIoUrl");
            return;
        }
        try {
            tusFileUploadService.process(request, response);
            final String uploadURI = request.getRequestURI();
            UploadInfo uploadInfo = null;
            try {
                uploadInfo = tusFileUploadService.getUploadInfo(uploadURI);
            }
            catch (IOException | TusException e) {
                throw new RuntimeException("get upload info", e);
            }
//            log.info("TUS上传预备 {}", uploadURI);
            if (uploadInfo != null && !uploadInfo.isUploadInProgress()) {
                final InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(uploadURI);
                try {
                    final String appUploadDirectory = tusProperties.getAppUploadDirectory().toString();
                    final Path outPut = Paths.get(appUploadDirectory).resolve(uploadInfo.getFileName());
                    String fileOss=minioUtil.uploadForTUS(uploadedBytes,uploadInfo,user);       //@上一次失败的情况，若+HEAD包:可能进这里。
//                    log.info("TUS上传fName {} uploadURI={}最后uRL={}", uploadInfo.getFileName(),uploadURI, fileOss);
                }
                catch (Exception e) {
                    throw new RuntimeException("upload空间满?", e);
                }
                uploadedBytes.close();
            }
            //if(null==uploadInfo)  response.setHeader("Tus2minIoUrl", "DO NOT TRY:uploadInfoNull");     //@被上面.process(request, response)牵着没法应答body;
        } catch (TusException | IOException e) {
            throw new RuntimeException("上传失败，原因：" + e.getMessage(), e);
        }
        //前端报错 httpStack Refused to get unsafe header "Location" Refused to get unsafe header "Upload-Offset" 上传扩展协议TUS
        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Location,Upload-Offset,Upload-Length,Tus2minIoUrl");
    }
    /** DELETE走这里
     *文档 https://uppy.io/docs/tus/ 文件断点续传的协议-Tus 协议  https://www.jianshu.com/p/57405bc1bc46
     * 若PATCH返回500的，前端会多次尝试上传，最后用户再去点×没有能删除遗留tus文件 多个的。
     * */
    public void uploadDel(HttpServletRequest request, HttpServletResponse response) {
        User user=base.checkAuth();
        if(user==null){
            response.setStatus(403);
            return;     //没有登录的，若经过下面的tusFileUploadService.process(request, response)就不会有应答new UploadFileResponse("WQA.Dac", "http://sdff111/ert6ertrE"
        }
        try {
            tusFileUploadService.process(request, response);
            final String uploadURI = request.getRequestURI();
            UploadInfo uploadInfo = null;
            try {
                uploadInfo = tusFileUploadService.getUploadInfo(uploadURI);
            }
            catch (IOException | TusException e) {
                throw new RuntimeException("get upload info", e);
            }
            log.info("TUS删除 {}", uploadURI);  //遇到没有上传成功的 DEL： 不会进入下面，倒是TUS目录滞留的临时文件有一堆！！
            if (uploadInfo != null && !uploadInfo.isUploadInProgress()) {
                final InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(uploadURI);
                // try (InputStream is = this.tusFileUploadService.getUploadedBytes(uploadURI)) { }
                try {
                    final String appUploadDirectory = tusProperties.getAppUploadDirectory().toString();
                    final Path outPut = Paths.get(appUploadDirectory).resolve(uploadInfo.getFileName());
                    //将后端已经完成端点续传的最终的文件 存入指定的MinIO存储；
                    String fileOss=minioUtil.uploadForTUS(uploadedBytes,uploadInfo,user);
//                    log.info("TUS上传fName {} uploadURI={}最后uRL={}", uploadInfo.getFileName(),uploadURI, fileOss);
                }
                catch (Exception e) {
                    throw new RuntimeException("uploadDel", e);
                }
                uploadedBytes.close();
            }
        } catch (TusException | IOException e) {
            throw new RuntimeException("上传失败，原因：" + e.getMessage(), e);
        }
        //前端报错 httpStack Refused to get unsafe header "Location" Refused to get unsafe header "Upload-Offset" 上传扩展协议TUS
        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Location,Upload-Offset,Upload-Length,Tus2minIoUrl");
    }

    /**
     *文档 https://uppy.io/docs/tus/ 文件断点续传的协议-Tus 协议  https://www.jianshu.com/p/57405bc1bc46
     * */
    public void uploadGet(HttpServletRequest request, HttpServletResponse response) {
        User user=base.checkAuth();
        if(user==null){
            response.setStatus(403);
            return;     //没有登录的
        }
        try {
            //没有支持GET操作的   tusFileUploadService.process(request, response);
            final String uploadURI = request.getRequestURI();
            UploadInfo uploadInfo = null;
            try {
                uploadInfo = tusFileUploadService.getUploadInfo(uploadURI);
            }
            catch (IOException | TusException e) {
                throw new RuntimeException("get upload info", e);
            }
            if (uploadInfo != null && !uploadInfo.isUploadInProgress()) {
                try {
                    tusFileUploadService.deleteUpload(uploadURI);
                }
                catch (IOException | TusException e) {
                    throw new RuntimeException("delete upload", e);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("上传Get失败，原因：" + e.getMessage(), e);
        }
        //前端报错 httpStack Refused to get unsafe header "Location" Refused to get unsafe header "Upload-Offset" 上传扩展协议TUS
        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Location");
    }
    //【下面】实际和 TUS 没关系的； 文件管理附带的功能。
    /**
     * 删除OSS文件； 只能设定一个TAG/key,上传时刻也一样的设定关键一个key;
     */
    public String deleteMinIO(String file, String key, String value) {
        User user = base.checkAuth();
        if (user == null) return "未登录";
        log.info("删除file={}", file);
        try {
            final String appUploadDirectory = tusProperties.getAppUploadDirectory().toString();
            int ifrom = file.indexOf("/");
            String objName = file.substring(ifrom + 1);
            String bucket = file.substring(0, ifrom);
            // 检查 MinIO 连接状态
            if (!minioUtil.bucketExists(bucket)) {
                log.error("MinIO服务不可用或bucket不存在: {}", bucket);
                return "OSS服务不可用，请检查对象存储集群服务状态";
            }
            // 获取元数据
            Map<String, String> metadata;
            try {
                metadata = minioUtil.getUserMetadata(bucket, objName, null);
            } catch (Exception e) {
                log.error("获取文件元数据失败，MinIO可能不可用: {}", e.getMessage());
                return "MinIO服务不可用，无法删除文件";
            }
            if (null == metadata) {
                return "文件不存在";
            }
            String who = metadata.get("author");
            if (StringUtils.hasText(who)) {
                if (!who.equals(user.getUsername()))
                    return "创建人才能删";
            }
            // 严格的Key ID正常也只能设定一个！也可能都没有
            String tagval = metadata.get(key);
            if (StringUtils.hasText(tagval)) {
                if (!tagval.equals(value)) return key + "不一致";
            }
            // 执行删除操作
            String errs;
            try {
                errs = minioUtil.remove(bucket, objName);
            } catch (Exception e) {
                log.error("删除文件时MinIO服务异常: {}", e.getMessage());
                return "MinIO服务异常，删除失败";
            }
            if (null == errs) return "成功";
            else return errs;
        } catch (Exception e) {
            log.error("删除文件过程中出现未知异常: {}", e.getMessage(), e);
            // 检查是否是MinIO连接相关的异常
            if (e.getMessage().contains("Connection refused") ||
                    e.getMessage().contains("ConnectException") ||
                    e.getMessage().contains("无法连接到") ||
                    e.getMessage().toLowerCase().contains("connection") ||
                    e.getMessage().toLowerCase().contains("connect")) {
                return "MinIO存储服务连接失败，请检查服务状态";
            }
            // 特别处理字符串索引越界异常，表明文件路径格式不正确
            if (e instanceof StringIndexOutOfBoundsException ||
                    e.getMessage().contains("out of bounds") ||
                    e.getMessage().contains("Range") && e.getMessage().contains("length")) {
                return "文件不存在";
            }
            return "删除失败: " + e.getMessage();
        }
    }
    /**
     * 处理普通 HTTP 文件上传到 MinIO
     */
    public String uploadToMinIO(MultipartFile file) {
        User user = base.checkAuth();
        if (user == null) {
            throw new RuntimeException("未登录");
        }
        return minioUtil.uploadFile(file, user);
    }

    /**
     * 处理普通 HTTP 多文件上传
     */
    public Map<String, String> uploadMultipleToMinIO(MultipartFile[] files) {
        User user = base.checkAuth();
        if (user == null) {
            throw new RuntimeException("未登录");
        }
        return minioUtil.uploadMultipleFiles(files, user);
    }
    /**
     * 处理普通 HTTP 文件上传到 MinIO，包含元数据
     */
    public String uploadToMinIOWithMeta(MultipartFile file,String business,String eid, int liveDays, String fileName) {
        User user = base.checkAuth();
        if (user == null) {
            throw new RuntimeException("未登录");
        }
        try {
            // 生成唯一文件名
            String originalFileName = file.getOriginalFilename();
            String fileExtension = "";
            if (originalFileName != null && originalFileName.contains(".")) {
                fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
            }
            //【minIO】内部都用id定位的对象，可是实际存储可能还是用磁盘文件系统的树形路径分解形式的。 使用类似 TUS 的路径格式
            SimpleDateFormat pathSdf = new SimpleDateFormat("/yyyyMM/ddHH/");
            String format = pathSdf.format(new Date());
            String objectName = format + UUID.randomUUID().toString() + fileExtension;

            // 设置元数据，类似于 TUS 的处理方式
            Map<String, String> metas = new HashMap<>();
            metas.put("author", user.getUsername());
            metas.put("business", business);
            metas.put("eid", eid);
            metas.put("filename", fileName);
            // 可以添加其他需要的元数据

            // 检查对象是否已存在
            StatObjectResponse oldStat = minioUtil.statObject(prop.getBucketName(), objectName, null);
            if (null != oldStat) {
                throw new RuntimeException("文件已存在: " + objectName);
            }

            // 设置保留期（如果需要）
            ZonedDateTime endLive = ZonedDateTime.now().plusDays(liveDays);

            PutObjectArgs objectArgs = PutObjectArgs.builder()
                    .bucket(prop.getBucketName())
                    .object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .retention(new Retention(RetentionMode.COMPLIANCE, endLive)) // 添加保留期设置
                    .userMetadata(metas)
                    .build();

            minioUtil.getMinioClient().putObject(objectArgs);

            // 返回文件访问链接 ID (bucketName/objectName)
            String fileUrl = prop.getBucketName() + objectName;

            // 验证上传是否成功
            StatObjectResponse newStat = minioUtil.statObject(prop.getBucketName(), objectName, null);
            if (null == newStat) {
                throw new RuntimeException("MinIO 上传验证失败: " + objectName);
            }

            log.info("XHR 文件上传到 MinIO 成功: {} -> {}, eid: {}, liveDays: {}",
                    originalFileName, fileUrl, eid, liveDays);

            return fileUrl;

        } catch (Exception e) {
            throw new RuntimeException("MinIO 上传失败: " + e.getMessage(), e);
        }
    }
}

/*上传扩展协议TUS 应该有的?
Access-Control-Allow-Headers:
Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable,
Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Draft-Interop-Version
* */